package com.googlecode.sparda.commons.lang.reflect;

/**
 * @author Fabio Grucci (<i>alias</i> <b>Phoenix</b>)
 * @version 0.0.1
 * @since 0.0.1
 */
public final class ReflectionUtils { // work only on Java 6

	private static final String UNSUPPORTED_BY_VM = "This virtual machine doesn't retrieve information about Thread.getStackTrace()";

	private static final String CURRENT_METHOD_ERROR_MESSAGE = "Cannot identify currentMethodName - probably you call this method from main(String[] args)";
	
	private static final String CALLING_METHOD_ERROR_MESSAGE = "Cannot identify callingMethodName - probably you call this method from main(String[] args)";
	
	private static final String CALLING_CLASS_ERROR_MESSAGE = "Cannot identify callingClassName - probably you call this method from main(String[] args)";
	
	private static final String WRONG_VALUE_FOR_PARAMETER = "you have wrong the <callToIgnore> value";
	
	private static final String CURRENT_METHOD_NAME_TAG = "<current_method_name>";
	private static final String CALLING_METHOD_NAME_TAG = "<calling_method_name>";
	
	@SuppressWarnings("unused")
	private static final String DETAILS_FORMAT_1 =	"Current Method Name:\t" + CURRENT_METHOD_NAME_TAG + "\n" +
													"Calling Method Name:\t" + CALLING_METHOD_NAME_TAG;
	
	private static final String DETAILS_FORMAT_2 =	"Method: name='" + CURRENT_METHOD_NAME_TAG + "' callingMethod='" + CALLING_METHOD_NAME_TAG + "'";
	
	private static final String UNKNOWN_CURRENT_METHOD =	"<Unknown Current Method>";
	private static final String UNKNOWN_CALLING_METHOD =	"<Unknown Calling Method>";
	
	private static final String NO_CALLING_METHOD =			"<No Calling Method>";
	
	/**
	 * Metodo che restituisce il nome del metodo chiamante il corrente.
	 * @return Una stringa rappresentante il nome del metodo chiamante
	 */
	public static String getCallingMethodsName() {
		try {
			return getCallingMethodsName(1);
		} catch (IllegalArgumentException e) {
			throw new IllegalStateException(CALLING_METHOD_ERROR_MESSAGE);
		}
	}
	
	/**
	 * Metodo che ritorna il nome della classe chiamante
	 * @return
	 */
	public static String getCallingClassName() {
		try {
			return getCallingClassName(1);
		} catch (IllegalArgumentException e) {
			throw new IllegalStateException(CALLING_CLASS_ERROR_MESSAGE);
		}
	}
	
	public static String getCallingClassName(int callToIgnore) {
		Thread currentThread = Thread.currentThread();
		StackTraceElement[] stackTrace = currentThread.getStackTrace();
		if(stackTrace.length == 0)
			throw new UnsupportedOperationException(UNSUPPORTED_BY_VM);
		if(stackTrace.length < 4 + callToIgnore)
			throw new IllegalArgumentException(CALLING_CLASS_ERROR_MESSAGE + " otherwise " + WRONG_VALUE_FOR_PARAMETER);
		StackTraceElement callingElement = stackTrace[3 + callToIgnore];
		return callingElement.getClassName();
	}
	
	/**
	 * Metodo che restituisce il nome del metodo chiamante, salendo nelle chiamate ignorate.
	 * @param callToIgnore Il numero di chiamate da ignorare
	 * @return Una stringa rappresentante il nome del metodo chiamante
	 */
	public static String getCallingMethodsName(int callToIgnore) {
		Thread currentThread = Thread.currentThread();
		StackTraceElement[] stackTrace = currentThread.getStackTrace();
		if(stackTrace.length == 0)
			throw new UnsupportedOperationException(UNSUPPORTED_BY_VM);
		if(stackTrace.length < 4 + callToIgnore)
			throw new IllegalArgumentException(CALLING_METHOD_ERROR_MESSAGE + " otherwise " + WRONG_VALUE_FOR_PARAMETER);
		StackTraceElement callingElement = stackTrace[3 + callToIgnore];
		return callingElement.getMethodName();
	}
	
	/**
	 * Metodo che ritorna il nome del metodo corrente.
	 * @return La stringa contenente il nome del metodo corrente
	 */
	public static String getCurrentMethodsName() {
		return getCurrentMethodsName(1);
	}
	/**
	 * Metodo che ritorna il nome del metodo corrente, ignorando le chiamate.
	 * @param callToIgnore Il numero di chiamate da ignorare
	 * @return La stringa contenente il nome del metodo
	 */
	public static String getCurrentMethodsName(int callToIgnore) {
		Thread currentThread = Thread.currentThread();
		StackTraceElement[] stackTrace = currentThread.getStackTrace();
		if(stackTrace.length == 0)
			throw new UnsupportedOperationException(UNSUPPORTED_BY_VM);
		if(stackTrace.length < 3 + callToIgnore)
			throw new IllegalArgumentException(CURRENT_METHOD_ERROR_MESSAGE + " " + WRONG_VALUE_FOR_PARAMETER);	//avviene solo se si sbaglia il numero
		StackTraceElement callingElement = stackTrace[2 + callToIgnore];
		return callingElement.getMethodName();
	}
	
	/**
	 * Metodo che stampa i dettagli sul metodo attuale.
	 */
	public static void printDetails() {
		System.out.println(getDetails(1));
	}
	
	/**
	 * Metodo che ritorna una stringa con i dettagli sul metodo attuale.
	 * @return La stringa con i dettagli del metodo
	 */
	public static String getDetails() {
		return getDetails(1);
	}
	
	/**
	 * Metodo che ritorna una stringa con i dettagli sul metodo attuale.
	 * @param callToIgnore Il numero di chiamate da ignorare
	 * @return La stringa con i dettagli del metodo
	 */
	public static String getDetails(int callToIgnore) {
		callToIgnore++;//perchè poi dentro richiamo getCallingMethods
		StringBuilder details = new StringBuilder(DETAILS_FORMAT_2);
		String currentMethodName;
		try {
			currentMethodName = getCurrentMethodsName(callToIgnore);
		} catch (UnsupportedOperationException e) {
			currentMethodName = UNKNOWN_CURRENT_METHOD;
		}
		String callingMethodName;
		try {
			callingMethodName = getCallingMethodsName(callToIgnore);
		} catch (UnsupportedOperationException e) {
			callingMethodName = UNKNOWN_CALLING_METHOD;
		} catch (IllegalArgumentException e) {
			callingMethodName = NO_CALLING_METHOD;
		}
		buildString(details, CURRENT_METHOD_NAME_TAG, currentMethodName , CALLING_METHOD_NAME_TAG, callingMethodName);
		return details.toString();
	}

	/**
	 * Costruisce la stringa popolando i tag contenuti in essa con i valori passati  
	 * @param actual la stringa da modificare
	 * @param tag_value_alternate Stringhe alternate tag - valore, quindi di lunghezza pari
	 * @return Lo {@link StringBuilder} modificato
	 */
	private static StringBuilder buildString(StringBuilder actual, String... tag_value_alternate) {
		for(int i=0; i+1 <tag_value_alternate.length; i+=2) {
			String tag		= tag_value_alternate[i];
			String value	= tag_value_alternate[i+1];
			int startIndex = actual.indexOf(tag);
			int endIndex = startIndex + tag.length();
			actual.replace(startIndex, endIndex, value);
		}
		return actual;
	}
	
}